﻿using CleanArchitecture.Core.Http;
using CleanArchitecture.Core.Interfaces.Cache;
using CleanArchitecture.Infrastructure.Configuration;
using CleanArchitecture.Infrastructure.Entities.AdminUsers;
using CleanArchitecture.Infrastructure.Entities.Business;
using CleanArchitecture.Infrastructure.Entities.Customers;
using CleanArchitecture.Infrastructure.Entities.Settings;
using CleanArchitecture.Infrastructure.Interfaces;
using CleanArchitecture.Infrastructure.Interfaces.AdminUsers;
using CleanArchitecture.Infrastructure.Interfaces.Authentication;
using CleanArchitecture.Infrastructure.Interfaces.Business;
using CleanArchitecture.Infrastructure.Interfaces.Customers;
using CleanArchitecture.Infrastructure.Services.Extensions;
using CleanArchitecture.Infrastructure.StartupConfig;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Localization;
using System;
using System.Linq;

namespace CleanArchitecture.Infrastructure.Services
{
    /// <summary>
    /// Represents work context for web application
    /// </summary>
    public partial class WebWorkContext : IWorkContext
    {
        #region Fields

        private readonly IAuthenticationService _authenticationService;
        private readonly IAdminUserService _adminuserService;
        private readonly IStaticCacheManager _cacheManager;
        private readonly IHttpContextAccessor _httpContextAccessor;
        private readonly SecuritySettings _securitySettings;
        private readonly ICustomerService _customerService;
        private readonly ILanguageService _languageService;

        private Language _cachedLanguage;
        private AdminUser _cachedAdminUser;
        private Customer _cachedCustomer;

        #endregion

        #region Ctor

        public WebWorkContext(IAuthenticationService authenticationService,
            IAdminUserService adminuserService,
            IStaticCacheManager cacheManager,
            IHttpContextAccessor httpContextAccessor,
            SecuritySettings securitySettings,
            ILanguageService languageService,
            ICustomerService customerService)
        {
            _cacheManager = cacheManager;
            _languageService = languageService;
            _customerService = customerService;
            _adminuserService = adminuserService;
            _securitySettings = securitySettings;
            _httpContextAccessor = httpContextAccessor;
            _authenticationService = authenticationService;
        }

        #endregion

        #region Utilities

        /// <summary>
        /// Get nop customer cookie
        /// </summary>
        /// <returns>String value of cookie</returns>
        protected virtual string GetAdminUserCookie()
        {
            var cookieName = $"{HttpDefineDefaults.Prefix}{HttpDefineDefaults.AdminUserCookie}";
            return _httpContextAccessor.HttpContext?.Request?.Cookies[cookieName];
        }

        /// <summary>
        /// Set nop customer cookie
        /// </summary>
        /// <param name="customerGuid">Guid of the customer</param>
        protected virtual void SetAdminUserCookie(string userMobile)
        {
            if (_httpContextAccessor.HttpContext?.Response == null)
                return;

            //delete current cookie value
            var cookieName = $"{HttpDefineDefaults.Prefix}{HttpDefineDefaults.AdminUserCookie}";
            _httpContextAccessor.HttpContext.Response.Cookies.Delete(cookieName);

            //get date of cookie expiration
            //DavidLee 用户登录Cookie有效期通过sys_setting表的securitysettings.cookieexpiredperiod设置
            int expiredtime = _securitySettings.CookieExpiredPeriod ?? 7;
            var cookieExpires = 24 * expiredtime; 
            var cookieExpiresDate = DateTime.Now.AddHours(cookieExpires);

            //if passed logedinUser Mobile is empty set cookie as expired
            if (string.IsNullOrEmpty(userMobile))
            {
                cookieExpiresDate = DateTime.Now.AddMonths(-1);
                userMobile = string.Empty;
            }

            //set new cookie value
            var options = new CookieOptions
            {
                HttpOnly = true, //DavidLee 2019/11/25 HttpOnly指定前端脚本是否能访问Cookie。
                                  //true:则前端脚本（如Javascript将无法通过cookie方法获取到值 
                Expires = cookieExpiresDate
            };
            _httpContextAccessor.HttpContext.Response.Cookies.Append(cookieName, userMobile, options);
        }

        /// <summary>
        /// Get language from the requested page URL
        /// </summary>
        /// <returns>The found language</returns>
        protected virtual Language GetLanguageFromUrl()
        {
            if (_httpContextAccessor.HttpContext?.Request == null)
                return null;

            //whether the requsted URL is localized
            var path = _httpContextAccessor.HttpContext.Request.Path.Value;
            if (!path.IsLocalizedUrl(_httpContextAccessor.HttpContext.Request.PathBase, false, out var language))
                return null;

            return language;
        }

        /// <summary>
        /// Get language from the request
        /// </summary>
        /// <returns>The found language</returns>
        protected virtual Language GetLanguageFromRequest()
        {
            if (_httpContextAccessor.HttpContext?.Request == null)
                return null;

            //get request culture
            var requestCulture = _httpContextAccessor.HttpContext.Features.Get<IRequestCultureFeature>()?.RequestCulture;
            if (requestCulture == null)
                return null;

            //try to get language by culture name
            var requestLanguage = _languageService.GetLanguagesAll().FirstOrDefault(language =>
                language.Culture.Equals(requestCulture.Culture.Name, StringComparison.InvariantCultureIgnoreCase));

            //check language availability
            if (requestLanguage == null || requestLanguage.IsActive == Entities.EntityEnumConfig.YesorNo.No)
                return null;

            return requestLanguage;
        }
        #endregion

        #region Properties

        #region Admin
        /// <summary>
        /// Gets or sets the current customer
        /// </summary>
        public virtual AdminUser CurrentAdminUser
        {
            get
            {
                //whether there is a cached value
                if (_cachedAdminUser != null)
                    return _cachedAdminUser;

                AdminUser user = null;

                //check whether request is made by a background (schedule) task
                if (_httpContextAccessor.HttpContext.Request.Path.Equals(new PathString($"/{DefaultRoutePath.ScheduleTaskPath}"), StringComparison.InvariantCultureIgnoreCase) ||
                    _httpContextAccessor.HttpContext.Request.Path.Equals(new PathString($"{DefaultRoutePath.ScheduleTaskPath}"), StringComparison.InvariantCultureIgnoreCase))

                {
                    //in this case return built-in customer record for background task
                    user = _adminuserService.GetAdminUserByLoginname("BuildInSystemTask");
                }
                else //try to get registered user
                    user = _authenticationService.GetAuthenticatedAdminUser();
                if (user != null)
                {
                    SetAdminUserCookie(user.Mobile);
                    //cache the found customer
                    _cachedAdminUser = user;
                }

                return user;
            }
            set
            {
                SetAdminUserCookie(value.Mobile);
                _cachedAdminUser = value;
            }
        }

        /// <summary>
        /// Gets or sets value indicating whether we're in admin area
        /// </summary>
        public virtual bool IsAdmin
        {
            get
            {
                try
                {
                    if (_cachedAdminUser == null) return false;
                    string cacheKey = string.Format(DefaultCacheConfig.AdminUserIsAdmin, _cachedAdminUser.Id);
                    return _cacheManager.Get(cacheKey, () =>
                    {
                        var roles = _cachedAdminUser.GetAdminRoles();
                        return roles.Any(role => role.IsAdmin == Entities.EntityEnumConfig.YesorNo.YES);
                    });
                }
                catch (Exception)
                {
                    return false;
                }

            }
        }
        #endregion

        #region Customer
        /// <summary>
        /// Gets or sets the current customer
        /// Customer from frontend using jwt authentication
        /// </summary>
        public virtual Customer CurrentCustomer
        {
            get
            {
                //whether there is a cached value
                if (_cachedCustomer != null)
                    return _cachedCustomer;

                if (_httpContextAccessor == null 
                 || _httpContextAccessor.HttpContext.Request == null)
                {
                    return null;
                }
                //try to get registered customer
                Customer customer = _authenticationService.GetAuthenticatedCustomer();
                if (customer != null) //cache the found customer
                    _cachedCustomer = customer;

                return customer;
            }
            set
            {
                _cachedCustomer = value;
            }
        }
        #endregion

        #region WorkingLanguage
        /// <summary>
        /// Gets or sets current user working language
        /// </summary>
        public virtual Language WorkingLanguage
        {
            get
            {
                //whether there is a cached value
                if (_cachedLanguage != null)
                    return _cachedLanguage;

                Language detectedLanguage = null;

                detectedLanguage = GetLanguageFromUrl();

                //whether we should detect the language from the request
                if (detectedLanguage == null)
                    detectedLanguage = GetLanguageFromRequest();

                //if there are no languages found try to get the first or default language
                if (detectedLanguage == null)
                    detectedLanguage = _languageService.GetLanguagesAll().FirstOrDefault();

                //cache the found language
                _cachedLanguage = detectedLanguage;

                return _cachedLanguage;
            }
            set
            {
                //get passed language identifier
                var languageId = value?.Id ?? 0;
                var lang = _languageService.GetLanguagesAll().FirstOrDefault(l => l.Id == languageId);
                if (lang != null)
                    _cachedLanguage = lang;
            }
        }
        #endregion

        #endregion
    }
}
